package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"mydocker/cgroups/resource"
	"mydocker/common"
	"mydocker/network"
	"mydocker/operations"
	"os"
)

// 创建 namespace 隔离的容器进程
// 启动容器
var runCommand = cli.Command{
	Name: "run", // 命令的名称
	Usage: `Create a container with namespace and cgroups limit
			mydocker run -it [command]
			mydocker run -d -name [containerName] [imageName] [command]`,
	Flags: []cli.Flag{ // 定义命令支持的命令行选项
		cli.BoolFlag{
			Name:  "it",         // 简单起见，这里把 -i 和 -t 参数合并成一个
			Usage: "enable tty", // 这个选项会让容器的进程在前台运行，并分配一个伪终端。这通常用于需要交互式控制台的情况。
		},
		cli.StringFlag{
			Name:  "mem", // 限制进程内存使用量，为了避免和 stress 命令的 -m 参数冲突 这里使用 -mem，到时候可以看下解决冲突的方法
			Usage: "memory limit, eg: -mem 100m",
		},
		cli.StringFlag{
			Name:  "cpu", // 限制进程的 cpu 使用率
			Usage: "cpu limit, eg: -cpu 100",
		},
		cli.StringFlag{
			Name:  "cpuset", // 设置 CPU 的亲和性，限制进程只能在指定的 cpu 上运行
			Usage: "cpuset limit, eg: -cpuset 2,4",
		},
		cli.StringFlag{
			Name:  "v", // 数据卷
			Usage: "docker volume, eg: -v /etc/conf:/etc/conf",
		},
		cli.BoolFlag{
			Name:  "d",                                // 是否以分离模式运行容器。
			Usage: "detach container, run background", // 这个选项会让容器的进程在后台运行，docker 会返回容器的 ID 并继续执行其他命令。这通常用于需要长时间运行的容器，不需要交互式控制台。
		},
		cli.StringFlag{
			Name:  "name",
			Usage: "container name, eg: -name myContainer", // 指定容器名称
		},
		cli.StringSliceFlag{
			Name:  "e",
			Usage: "set environment, eg: -e name=mydocker", // 指定环境变量
		},
		cli.StringFlag{
			Name:  "net",
			Usage: "container network, eg: -net testBridge", // 指定容器的网络
		},
		cli.StringSliceFlag{
			Name:  "p",
			Usage: "port mapping, eg: -p 8080:80 -p 13306:3306", // 指定端口映射
		},
	},
	// 这里是 run 命令执行的真正函数
	// 1.判断参数是否包含 command
	// 2.获取用户指定的 command
	// 3.调用 Run function 去准备启动容器
	Action: func(ctx *cli.Context) error { // 定义命令执行时的回调函数（作为另一个函数的参数）
		if len(ctx.Args()) < 1 { // 检查是否提供了足够的参数
			return fmt.Errorf("missing container command")
		}

		// 要运行的镜像名
		imageName := ctx.Args().Get(0) // 返回 args() 切片中索引为 0 的元素
		// cmdArray 为容器运行后，执行的第一个命令信息
		// cmdArray[0] 为镜像名，.Tail() 是去掉第一个后的全部参数
		var cmdArray []string
		for _, arg := range ctx.Args().Tail() { // ctx.Args() 返回传递给命令行应用程序的非标志参数（即不以连字符 - 或 -- 开头的参数）
			cmdArray = append(cmdArray, arg)
		}
		logrus.Infof("imageName: %s", imageName)

		tty := ctx.Bool("it") // 获取 tty 标志的值(looks up the value of a local BoolFlag)
		detach := ctx.Bool("d")
		if tty && detach { // docker 将无法决定容器应该在前台运行还是在后台运行，会导致逻辑上的冲突，因为一个容器不能同时在前台和后台运行。
			return fmt.Errorf("tty and detach cannot be set at the same time")
		}
		if !detach { // 如果不是指定后台运行，就默认前台运行
			tty = true
		}
		logrus.Infof("createTty %v", tty)

		res := &resource.ResourceConfig{ // 创建资源配置对象
			MemoryLimit: ctx.String("mem"),
			CpuCFSQuota: ctx.Int("cpu"),
			CpuSet:      ctx.String("cpuset"),
		}
		if res.CpuCFSQuota == 0 {
			res.CpuCFSQuota = common.DefaultCpuCFSQuota
		}
		if res.MemoryLimit == "" {
			res.MemoryLimit = common.DefaultMemoryLimit
		}
		logrus.Info("resourceConfig:", res)

		containerName := ctx.String("name")
		volume := ctx.String("v")
		net := ctx.String("net")
		envs := ctx.StringSlice("e")
		ports := ctx.StringSlice("p")

		operations.Run(cmdArray, tty, res, containerName, imageName, volume, net, envs, ports) // 启动容器
		return nil
	},
}

// 初始化容器内容，挂载 proc 文件系统，运行用户执行程序
var initCommand = cli.Command{
	Name:  "init",
	Usage: "Init container process run user's process in container. Do not call it outside",
	Action: func(ctx *cli.Context) error {
		logrus.Infof("Init come on")
		return operations.RunContainerInitProcess()
	},
}

// 将 docker 容器导出为镜像
var commitCommand = cli.Command{
	Name:  "commit",
	Usage: "commit container to image. eg: mydocker commit 123456789 myimage",
	Action: func(ctx *cli.Context) error {
		if len(ctx.Args()) < 2 {
			return fmt.Errorf("commit container to image")
		}
		containerID := ctx.Args().Get(0)
		imageName := ctx.Args().Get(1)
		return operations.CommitContainer(containerID, imageName)
	},
}

var listCommand = cli.Command{
	Name:  "ps",
	Usage: "list all containers",
	Action: func(ctx *cli.Context) error {
		operations.ListContainersInfo()
		return nil
	},
}

var logCommand = cli.Command{
	Name:  "logs",
	Usage: "look container logs. eg: mydocker logs myContainerID",
	Action: func(ctx *cli.Context) error {
		if len(ctx.Args()) < 1 {
			return fmt.Errorf("missing container name")
		}
		containerID := ctx.Args().Get(0)
		operations.LookContainerLog(containerID)
		return nil
	},
}

var execCommand = cli.Command{
	Name:  "exec",
	Usage: "exec a command into container. eg: mydocker exec 123456789 /bin/sh", // 进入容器中
	Action: func(ctx *cli.Context) error {
		// 如果环境变量里面有 PID，那么则什么都不用执行，说明 C 代码已经运行过了，即 setns 系统调用已经执行了，这里就直接返回，避免重复执行
		pid := os.Getenv(common.EnvExecPid)
		if pid != "" {
			logrus.Infof("pid callback pid %s, gid: %d", pid, os.Getgid())
			return nil
		}
		// 格式：mydocker exec 容器ID 命令，因此至少会有两个参数
		if len(ctx.Args()) < 2 {
			return fmt.Errorf("missing container name or command")
		}

		var cmdArray []string
		for _, arg := range ctx.Args().Tail() { // 返回去掉第一个后的全部参数
			cmdArray = append(cmdArray, arg)
		}

		containerID := ctx.Args().Get(0)
		operations.ExecContainer(containerID, cmdArray)
		return nil
	},
}

var stopCommand = cli.Command{
	Name:  "stop",
	Usage: "stop a container. eg: nydocker rm 123456789",
	Action: func(ctx *cli.Context) error {
		// 期望输入时：mydocker stop 容器ID，如果没有指定参数直接打印错误
		if len(ctx.Args()) < 1 {
			return fmt.Errorf("missing stop container name")
		}
		containerID := ctx.Args().Get(0)
		operations.StopContainer(containerID)
		return nil
	},
}

var removeCommand = cli.Command{
	Name:  "rm",
	Usage: "remove unused containers. eg: mydocker rm 123456789",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "f", // 强制删除
			Usage: "force delete running container",
		},
	},
	Action: func(ctx *cli.Context) error {
		if len(ctx.Args()) < 1 {
			return fmt.Errorf("missing remove container name")
		}
		containerID := ctx.Args().Get(0)
		force := ctx.Bool("f")
		operations.RemoveContainer(containerID, force)
		return nil
	},
}

var networkCommand = cli.Command{
	Name:  "network",
	Usage: "container network commands",
	Subcommands: []cli.Command{
		{
			Name:  "create",
			Usage: "create a container network",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "driver",
					Usage: "network driver", // 指定网络驱动
				},
				cli.StringFlag{
					Name:  "subnet",
					Usage: "subnet cidr", // 指定子网的 CIDR 表示法
				},
			},
			Action: func(ctx *cli.Context) error {
				if len(ctx.Args()) < 1 {
					return fmt.Errorf("missing network name")
				}
				network.Init()
				driver := ctx.String("driver")
				subnet := ctx.String("subnet")
				name := ctx.Args().Get(0)

				err := network.CreateNetwork(driver, subnet, name)
				if err != nil {
					return fmt.Errorf("create network err: %v", err)
				}
				return nil
			},
		},
		{
			Name:  "list",
			Usage: "list container network",
			Action: func(ctx *cli.Context) error {
				network.Init()
				network.ListNetwork()
				return nil
			},
		},
		{
			Name:  "remove",
			Usage: "remove container network",
			Action: func(ctx *cli.Context) error {
				if len(ctx.Args()) < 1 {
					return fmt.Errorf("missing network name")
				}
				network.Init()
				err := network.DeleteNetwork(ctx.Args()[0])
				if err != nil {
					return fmt.Errorf("remove network err: %v", err)
				}
				return nil
			},
		},
	},
}
